package com.lu.study.lu.base.framework.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.InputStreamSource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @autor 10758
 * @system lu-pro-arch
 * @Time 2018/12/15
 */
@Aspect
@Slf4j
@Configuration
@Order(-1)
public class SysLogAop {

    /**
     * 请求入参格式
     */
    private final static String REQ_FORMAT = "Method={},Url={} ,Parameters={}";

    /**
     * 请求出参格式
     */
    private final static String RES_FORMAT = "CostTime={}ms,Method={},Url={} ,Result={}";

    /**
     * 超时格式
     */
    private final static String OPTIMIZE_INTERFACE_FORMAT = "Url={} ,耗时超过{}秒，请对方法进行优化";

    /**
     * 异常格式
     */
    private final static String EXCEPTION_FORMAT = "Method={},Url={} ,Parameters={} 发生异常, ex={}";

    @Value("${lu.request.optimization.mintime:3000}")
    private Long maxTime;

    @Pointcut(" @target(org.springframework.stereotype.Controller) || "
            + " @target(org.springframework.web.bind.annotation.RestController) ")
    public void controllerPointcut() {
    }

    @Pointcut(" @annotation(org.springframework.web.bind.annotation.RequestMapping) " + " || @annotation(org.springframework.web.bind.annotation.PostMapping) " + " || @annotation(org.springframework.web.bind.annotation.GetMapping) ")
    public void methodPointcut() {
    }

    private static boolean filterBigParam(Object o) {
        return !(o instanceof File || o instanceof InputStreamSource || o instanceof OutputStream
                || o instanceof ServletRequest || o instanceof ServletResponse || o instanceof InputStream
                || o instanceof Writer);
    }

    @Around("controllerPointcut() && methodPointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest request = getRequest();
        Long startTime = System.currentTimeMillis();
        String paramsStr = getParamsStr(pjp, Object::toString);

        // 打印请求url 入参
        // if (log.isDebugEnabled()) {
        log.info(REQ_FORMAT, request.getMethod(), request.getRequestURL(), paramsStr);
        //}

        // 开始执行原始请求（执行controller）
        // 不捕获 pjp.proceed() 异常 不然会吞掉异常
        Object result = pjp.proceed();

        Long cost = System.currentTimeMillis() - startTime;
        // 打印请求url 出参 耗时时间
        // if (log.isDebugEnabled()) {
        log.info(RES_FORMAT, cost, request.getMethod(), request.getRequestURL(),
                Optional.ofNullable(request).isPresent() ? result.toString() : "null");
        //}
        // 提示是否需要对请求进行优化
        if (cost > maxTime) {
            log.info(OPTIMIZE_INTERFACE_FORMAT, request.getRequestURL(), maxTime / 1000);
        }

        return result;

    }

    @AfterThrowing(pointcut = "controllerPointcut() && methodPointcut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        HttpServletRequest request = getRequest();
        String paramsStr = getParamsStr(joinPoint, Object::toString);
        log.error(EXCEPTION_FORMAT, request.getMethod(), request.getRequestURL(), paramsStr, ex);
    }

    private HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }

    private String getParamsStr(JoinPoint joinPoint, Function<Object, String> function) {
        Object[] args = joinPoint.getArgs();
        String paramsStr = Stream.of(args).filter(SysLogAop::filterBigParam)
                .filter(v -> null != v)
                .map(o -> function.apply(o))
                .collect(Collectors.joining(","));
        return null == paramsStr ? "" : paramsStr;
    }
}
